// converter_encrypted.js - 基于AES-128和RSA的视频DRM加密系统
// 该系统实现了一个类似DRM的视频内容保护系统，通过以下步骤保护视频内容：
// 1. 使用AES-128加密视频内容（HLS格式）
// 2. 使用RSA公钥加密AES密钥
// 3. 通过JWT令牌授权系统控制密钥访问
// 4. 提供Web播放器界面与授权服务集成

// 导入必要的库
const ffmpeg = require("fluent-ffmpeg")  // 用于视频转换和加密
const fs = require("fs")                 // 文件系统操作
const path = require("path")             // 路径处理
const crypto = require("crypto")         // 加密功能
const express = require("express")       // Web服务器
const jwt = require("jsonwebtoken")      // JWT令牌生成和验证
const cors = require("cors")             // 跨域资源共享
const rateLimit = require("express-rate-limit")  // 请求频率限制
const { URL } = require("url")           // URL解析

// 定义输入输出路径
const input = path.resolve(__dirname, "input2.mp4")  // 要加密的源视频文件
const output = path.resolve(__dirname, "public", "encrypted")  // 加密后的输出目录
const keyServerPort = 3000  // DRM授权服务器端口

// DRM系统配置
const DRM_CONFIG = {
  SECRET_KEY: "your-very-secret-key-change-in-production", // JWT密钥，生产环境应更改
  TOKEN_EXPIRE: "1h",                    // 令牌过期时间（1小时）
  LICENSE_SERVER: `http://localhost:${keyServerPort}`, // 授权服务器地址
  ALLOWED_ORIGINS: [                     // 允许的来源域名白名单
    "http://localhost:3000", 
    "https://yourdomain.com"
  ],
  ALLOWED_PLAYER_IDS: ["hlsjs-player", "custom-player-id"], // 允许的播放器ID白名单
  MAX_KEY_REQUESTS_PER_MINUTE: 10,       // 每分钟最大密钥请求次数（防止暴力攻击）
  MAX_KEY_REQUESTS_PER_TOKEN: 5,         // 每个令牌的最大请求次数（防止令牌滥用）
  ENABLE_IP_BINDING: true                // 是否启用IP绑定（防止令牌盗用）
}

/**
 * 生成RSA密钥对（2048位）
 * 用于加密和解密AES密钥
 * @returns {Object} 包含publicKey和privateKey的对象
 */
function generateRSAKeyPair() {
  console.log("正在生成RSA密钥对...")
  // 使用Node.js的crypto模块生成RSA密钥对
  const { publicKey, privateKey } = crypto.generateKeyPairSync("rsa", {
    modulusLength: 2048,                 // 密钥长度（位）
    publicKeyEncoding: {                 // 公钥编码格式
      type: "spki",
      format: "pem"
    },
    privateKeyEncoding: {                // 私钥编码格式
      type: "pkcs8",
      format: "pem"
    }
  })

  // 创建密钥存储目录
  const keyDir = path.join(__dirname, "keys")
  if (!fs.existsSync(keyDir)) {
    fs.mkdirSync(keyDir, { recursive: true })
  }

  // 将密钥保存到文件
  fs.writeFileSync(path.join(keyDir, "rsa_public.pem"), publicKey)
  fs.writeFileSync(path.join(keyDir, "rsa_private.pem"), privateKey)

  console.log("RSA密钥对已生成")
  return { publicKey, privateKey }
}

/**
 * 使用RSA公钥加密AES密钥
 * @param {Buffer} aesKey - 要加密的AES密钥
 * @param {string} publicKey - RSA公钥
 * @returns {Buffer} 加密后的AES密钥
 */
function encryptAESKey(aesKey, publicKey) {
  // 使用RSA-OAEP填充方式加密
  const encryptedKey = crypto.publicEncrypt(
    {
      key: publicKey,
      padding: crypto.constants.RSA_PKCS1_OAEP_PADDING
    },
    aesKey
  )
  return encryptedKey
}

/**
 * 使用RSA私钥解密AES密钥
 * @param {Buffer} encryptedAESKey - 加密的AES密钥
 * @param {string} privateKey - RSA私钥
 * @returns {Buffer} 解密后的AES密钥
 */
function decryptAESKey(encryptedAESKey, privateKey) {
  // 使用RSA-OAEP填充方式解密
  const decryptedKey = crypto.privateDecrypt(
    {
      key: privateKey,
      padding: crypto.constants.RSA_PKCS1_OAEP_PADDING
    },
    encryptedAESKey
  )
  return decryptedKey
}

/**
 * 生成16字节的随机AES加密密钥
 * @returns {Buffer} 16字节的随机密钥
 */
function generateAESKey() {
  return crypto.randomBytes(16)  // AES-128需要16字节密钥
}

/**
 * 创建密钥信息文件，用于FFmpeg HLS加密
 * @param {string} keyInfoPath - 密钥信息文件路径
 * @param {string} keyUri - 密钥URI（播放器获取密钥的地址）
 * @param {string} keyPath - 密钥文件的本地路径
 * @param {Buffer|null} iv - 初始化向量（可选）
 * @returns {string} 密钥信息文件路径
 */
function createKeyInfoFile(keyInfoPath, keyUri, keyPath, iv = null) {
  // 如果提供了IV，转换为十六进制字符串
  const ivString = iv ? iv.toString("hex") : ""
  // 创建密钥信息内容（按FFmpeg要求的格式）
  const keyInfoContent = `${keyUri}\n${keyPath}\n${ivString}`
  // 写入文件
  fs.writeFileSync(keyInfoPath, keyInfoContent)
  return keyInfoPath
}

/**
 * 启动DRM授权服务器
 * 提供密钥授权、用户认证和安全验证功能
 * @param {string} encryptedKeyPath - 加密的AES密钥路径
 * @param {string} rsaPrivateKeyPath - RSA私钥路径
 * @param {number} port - 服务器端口
 * @param {Buffer} iv - 初始化向量
 */
function startDRMServer(encryptedKeyPath, rsaPrivateKeyPath, port, iv) {
  const app = express()  // 创建Express应用

  // 启用CORS，但仅限于允许的来源
  app.use(cors({
    origin: function(origin, callback) {
      // 允许没有来源的请求（如curl、移动应用等）
      if (!origin) return callback(null, true)
      
      // 检查来源是否在允许列表中
      if (DRM_CONFIG.ALLOWED_ORIGINS.indexOf(origin) !== -1) {
        return callback(null, true)
      } else {
        return callback(new Error('不允许的来源'), false)
      }
    },
    credentials: true // 允许携带凭据（如cookies）
  }))
  
  app.use(express.json())  // 解析JSON请求体

  // 静态文件服务，提供加密的HLS文件和播放器页面
  app.use(express.static(path.join(__dirname, "public")))
  
  // 创建令牌使用记录存储（用于跟踪令牌使用情况）
  const tokenUsageMap = new Map()
  
  // 创建IP绑定记录存储（用于令牌与IP绑定）
  const tokenIpMap = new Map()

  // 用户认证数据库(示例)，实际应用中应使用数据库存储
  const users = {
    user1: { password: "password1", role: "basic" },
    user2: { password: "password2", role: "premium" }
  }

  /**
   * 授权端点 - 生成访问令牌
   * POST /auth/token
   * 请求体: { username, password }
   * 响应: { token, expires_in, token_type }
   */
  app.post("/auth/token", (req, res) => {
    const { username, password } = req.body

    // 验证用户凭据
    if (!users[username] || users[username].password !== password) {
      return res.status(401).json({ error: "无效的用户名或密码" })
    }
    
    // 获取客户端IP，用于IP绑定
    const clientIp = req.headers['x-forwarded-for'] || req.connection.remoteAddress

    // 创建JWT令牌，包含用户信息和权限
    const token = jwt.sign(
      {
        username,
        role: users[username].role,
        keyAccess: true,  // 授予密钥访问权限
        clientIp: DRM_CONFIG.ENABLE_IP_BINDING ? clientIp : undefined,  // 如果启用IP绑定，记录客户端IP
        jti: crypto.randomBytes(16).toString('hex')  // 添加唯一令牌ID，用于跟踪和撤销
      },
      DRM_CONFIG.SECRET_KEY,
      { expiresIn: DRM_CONFIG.TOKEN_EXPIRE }
    )
    
    // 初始化令牌使用记录
    const jti = jwt.decode(token).jti
    tokenUsageMap.set(jti, { count: 0, lastAccess: Date.now() })
    
    // 如果启用IP绑定，记录令牌与IP的关联
    if (DRM_CONFIG.ENABLE_IP_BINDING) {
      tokenIpMap.set(jti, clientIp)
    }

    // 返回令牌信息
    res.json({
      token,
      expires_in: 3600, // 1小时(秒)
      token_type: "Bearer"
    })
  })

  // 速率限制器 - 限制密钥请求频率，防止暴力攻击
  const keyRequestLimiter = rateLimit({
    windowMs: 60 * 1000,  // 1分钟时间窗口
    max: DRM_CONFIG.MAX_KEY_REQUESTS_PER_MINUTE,  // 最大请求次数
    message: { error: '请求频率过高，请稍后再试' },
    standardHeaders: true,  // 返回标准的RateLimit头
    legacyHeaders: false,
  })

  /**
   * JWT验证中间件
   * 验证请求中的JWT令牌，并检查权限
   */
  const verifyToken = (req, res, next) => {
    const authHeader = req.headers.authorization

    // 检查Authorization头是否存在且格式正确
    if (!authHeader || !authHeader.startsWith("Bearer ")) {
      return res.status(401).json({ error: "未提供授权令牌" })
    }

    const token = authHeader.split(" ")[1]

    try {
      // 验证JWT令牌
      const decoded = jwt.verify(token, DRM_CONFIG.SECRET_KEY)

      // 检查令牌是否有权访问密钥
      if (!decoded.keyAccess) {
        return res.status(403).json({ error: "令牌无权访问加密密钥" })
      }
      
      // 检查令牌ID是否存在
      if (!decoded.jti) {
        return res.status(403).json({ error: "无效的令牌ID" })
      }
      
      // 检查令牌使用次数
      const tokenUsage = tokenUsageMap.get(decoded.jti)
      if (!tokenUsage) {
        // 令牌首次使用，初始化使用记录
        tokenUsageMap.set(decoded.jti, { count: 1, lastAccess: Date.now() })
      } else {
        // 检查使用次数是否超过限制
        if (tokenUsage.count >= DRM_CONFIG.MAX_KEY_REQUESTS_PER_TOKEN) {
          return res.status(429).json({ error: "令牌使用次数已达上限" })
        }
        
        // 更新使用记录
        tokenUsage.count++
        tokenUsage.lastAccess = Date.now()
        tokenUsageMap.set(decoded.jti, tokenUsage)
      }
      
      // 如果启用IP绑定，验证客户端IP
      if (DRM_CONFIG.ENABLE_IP_BINDING && decoded.clientIp) {
        const clientIp = req.headers['x-forwarded-for'] || req.connection.remoteAddress
        if (decoded.clientIp !== clientIp) {
          return res.status(403).json({ error: "IP地址不匹配，可能存在令牌盗用" })
        }
      }

      // 将解码后的令牌信息添加到请求对象
      req.user = decoded
      next()
    } catch (err) {
      // 处理不同类型的令牌错误
      if (err.name === "TokenExpiredError") {
        return res.status(401).json({ error: "令牌已过期" })
      }
      return res.status(403).json({ error: "无效的令牌" })
    }
  }
  
  /**
   * 验证请求来源和播放器中间件
   * 检查请求的来源、Referer、播放器ID等
   */
  const verifyRequestSource = (req, res, next) => {
    // 检查Referer头
    const referer = req.headers.referer
    if (referer) {
      try {
        const refererUrl = new URL(referer)
        const origin = refererUrl.origin
        
        // 检查来源是否在允许列表中
        if (!DRM_CONFIG.ALLOWED_ORIGINS.includes(origin)) {
          return res.status(403).json({ error: "不允许的请求来源" })
        }
      } catch (err) {
        return res.status(403).json({ error: "无效的Referer头" })
      }
    }
    
    // 检查播放器ID (如果有)
    const playerId = req.headers['x-player-id']
    if (playerId && !DRM_CONFIG.ALLOWED_PLAYER_IDS.includes(playerId)) {
      return res.status(403).json({ error: "不允许的播放器" })
    }
    
    // 检查User-Agent，过滤爬虫和机器人
    const userAgent = req.headers['user-agent']
    if (!userAgent || userAgent.toLowerCase().includes('bot') || userAgent.toLowerCase().includes('crawler')) {
      return res.status(403).json({ error: "不允许的用户代理" })
    }
    
    next()
  }

  /**
   * 密钥请求端点 - 需要有效的JWT令牌
   * GET /drm/key
   * 返回解密后的AES密钥
   */
  app.get("/drm/key", keyRequestLimiter, verifyRequestSource, verifyToken, (req, res) => {
    try {
      // 记录请求信息用于审计
      const requestInfo = {
        timestamp: new Date().toISOString(),
        username: req.user.username,
        ip: req.headers['x-forwarded-for'] || req.connection.remoteAddress,
        userAgent: req.headers['user-agent'],
        referer: req.headers.referer || 'unknown'
      }
      console.log("密钥请求:", JSON.stringify(requestInfo))
      
      // 读取加密的AES密钥
      const encryptedKey = fs.readFileSync(encryptedKeyPath)

      // 读取RSA私钥
      const privateKey = fs.readFileSync(rsaPrivateKeyPath)

      // 使用RSA私钥解密AES密钥
      const aesKey = decryptAESKey(encryptedKey, privateKey)

      // 可以基于用户角色添加更多访问控制
      if (req.user.role === "premium") {
        // 高质量密钥或其他特权
        console.log("Premium用户访问密钥")
      }

      // 记录密钥访问
      console.log(`用户 ${req.user.username} 访问了密钥`)
      
      // 添加安全响应头，防止缓存密钥
      res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate')
      res.setHeader('Pragma', 'no-cache')
      res.setHeader('Expires', '0')
      res.setHeader('Surrogate-Control', 'no-store')
      res.setHeader('Content-Type', 'application/octet-stream')
      
      // 返回解密后的AES密钥
      res.send(aesKey)
    } catch (err) {
      console.error("密钥处理错误:", err)
      res.status(500).json({ error: "服务器错误" })
    }
  })
  
  /**
   * IV获取端点 - 需要有效的JWT令牌
   * GET /drm/iv
   * 返回IV的十六进制表示
   */
  app.get("/drm/iv", verifyToken, (req, res) => {
    try {
      // 只有授权用户才能获取IV
      console.log(`用户 ${req.user.username} 获取了IV`)
      
      // 返回IV的十六进制表示
      res.json({ 
        iv: `0x${iv.toString('hex')}`,
        format: 'hex'
      })
    } catch (err) {
      console.error("IV处理错误:", err)
      res.status(500).json({ error: "服务器错误" })
    }
  })

  /**
   * 许可证信息端点
   * GET /drm/license-info
   * 返回DRM系统的许可证信息
   */
  app.get("/drm/license-info", (req, res) => {
    res.json({
      license_server: DRM_CONFIG.LICENSE_SERVER,
      key_endpoint: "/drm/key",
      iv_endpoint: "/drm/iv",
      auth_endpoint: "/auth/token",
      supported_drm: ["aes-128"]
    })
  })

  /**
   * 公钥端点 - 客户端可以获取公钥用于加密
   * GET /drm/public-key
   * 返回RSA公钥
   */
  app.get("/drm/public-key", (req, res) => {
    try {
      const publicKeyPath = path.join(__dirname, "keys", "rsa_public.pem")
      const publicKey = fs.readFileSync(publicKeyPath, "utf8")
      res.setHeader("Content-Type", "text/plain")
      res.send(publicKey)
    } catch (err) {
      console.error("公钥获取错误:", err)
      res.status(500).json({ error: "服务器错误" })
    }
  })
  
  // 定期清理过期的令牌使用记录，防止内存泄漏
  setInterval(() => {
    const now = Date.now()
    for (const [jti, usage] of tokenUsageMap.entries()) {
      // 清理超过2小时未访问的记录
      if (now - usage.lastAccess > 2 * 60 * 60 * 1000) {
        tokenUsageMap.delete(jti)
        if (DRM_CONFIG.ENABLE_IP_BINDING) {
          tokenIpMap.delete(jti)
        }
      }
    }
  }, 30 * 60 * 1000) // 每30分钟执行一次清理

  // 启动服务器
  app.listen(port, () => {
    console.log(`DRM授权服务器运行在端口 ${port}`)
    console.log(`授权端点: http://localhost:${port}/auth/token`)
    console.log(`密钥端点: http://localhost:${port}/drm/key`)
  })
}

/**
 * 将视频转换为加密的HLS格式
 * 实现视频加密的主要函数
 * @param {string} inputPath - 输入视频文件路径
 * @param {string} outputDir - 输出目录路径
 * @returns {Promise<string>} 输出m3u8文件的路径
 */
function convertToEncryptedHLS(inputPath, outputDir) {
  return new Promise((resolve, reject) => {
    // 检查输入文件是否存在
    if (!fs.existsSync(inputPath)) {
      reject(new Error(`输入文件不存在: ${inputPath}`))
      console.log(`输入文件不存在: ${inputPath}`)
      return
    }

    // 确保输出目录存在
    if (!fs.existsSync(outputDir)) {
      fs.mkdirSync(outputDir, { recursive: true })
      console.log("输出目录创建成功")
    }

    // 1. 生成RSA密钥对，用于加密AES密钥
    const { publicKey, privateKey } = generateRSAKeyPair()
    const privateKeyPath = path.join(__dirname, "keys", "rsa_private.pem")

    // 2. 生成AES密钥，用于加密视频内容
    const aesKey = generateAESKey()
    console.log("AES密钥已生成")
    
    // 3. 生成随机IV (16字节)，增强加密安全性
    const iv = crypto.randomBytes(16)
    console.log(`随机IV已生成: 0x${iv.toString('hex')}`)
    
    // 4. 使用RSA公钥加密AES密钥，保护密钥安全
    const encryptedAESKey = encryptAESKey(aesKey, publicKey)

    // 保存加密后的AES密钥，这是提供给授权服务器的版本
    const keyName = "encryption.key"
    const encryptedKeyPath = path.join(outputDir, keyName)
    fs.writeFileSync(encryptedKeyPath, encryptedAESKey)
    console.log(`AES密钥已用RSA加密并保存: ${encryptedKeyPath}`)

    // 保存原始AES密钥(仅用于测试，实际使用时应删除)
    fs.writeFileSync(path.join(outputDir, "original.key"), aesKey)
    
    // 保存IV (仅用于调试)
    fs.writeFileSync(path.join(outputDir, "iv.txt"), `0x${iv.toString('hex')}`)

    // 为FFmpeg创建临时AES密钥文件(转换完成后将删除)
    const tempKeyPath = path.join(outputDir, "temp.key")
    fs.writeFileSync(tempKeyPath, aesKey)

    // 设置密钥服务器URL - 指向DRM服务器
    const keyServerUrl = `${DRM_CONFIG.LICENSE_SERVER}/drm/key`
    
    // 创建密钥信息文件，包含IV，用于FFmpeg加密
    const keyInfoPath = path.join(outputDir, "enc_key_info.keyinfo")
    createKeyInfoFile(keyInfoPath, keyServerUrl, tempKeyPath, iv)
    console.log(`密钥信息文件已创建: ${keyInfoPath}`)

    console.log(`开始加密转换: ${inputPath} -> ${outputDir}`)

    // 使用FFmpeg将视频转换为加密的HLS格式
    ffmpeg(inputPath)
      .videoCodec("libx264")  // 使用H.264视频编码
      .audioCodec("aac")      // 使用AAC音频编码
      .outputOptions([
        "-hls_time 10",       // 每个分段10秒
        "-hls_list_size 0",   // 包含所有分段（0表示不限制）
        `-hls_segment_filename ${outputDir}/segment_%03d.ts`, // 分段文件命名格式
        `-hls_key_info_file ${keyInfoPath}`, // 密钥信息文件路径
        "-hls_playlist_type vod", // 点播类型（video on demand）
        "-f hls"              // 输出格式为HLS
      ])
      .output(`${outputDir}/index.m3u8`)  // 输出的m3u8文件路径
      .on("start", commandLine => {
        console.log("FFmpeg命令:", commandLine)
      })
      .on("progress", progress => {
        if (progress.percent) {
          console.log(`转换进度: ${Math.round(progress.percent)}%`)
        }
      })
      .on("stderr", stderrLine => {
        // 输出FFmpeg的stderr信息（可选）
        // console.log("FFmpeg输出:", stderrLine);
      })
      .on("end", () => {
        console.log("加密转换完成!")

        // 删除临时AES密钥文件，增强安全性
        try {
          fs.unlinkSync(tempKeyPath)
          console.log("临时密钥文件已删除")
        } catch (err) {
          console.error("删除临时密钥文件失败:", err)
        }
        
        // 在启动DRM服务器时传递IV，用于后续解密
        startDRMServer(encryptedKeyPath, privateKeyPath, keyServerPort, iv)
        
        console.log("DRM授权服务已启动，客户端需要授权才能获取解密密钥")
        resolve(`${outputDir}/index.m3u8`)
      })
      .on("error", (err, stdout, stderr) => {
        console.error("加密转换失败:", err.message)
        reject(err)
      })
      .run()  // 开始执行FFmpeg命令
  })
}

/**
 * 调用转换函数开始整个加密流程
 */
convertToEncryptedHLS(input, output)
  .then(res => {
    console.log(res)  // 输出结果文件路径
  })
  .catch(err => {
    console.error(err.message)  // 输出错误信息
  })
